/*
	Copyright (C) 2010 Salil Bhagurkar

	This file is part of k3

	k3 is free software: you can redistribute it and/or modify
	it under the terms of the GNU Lesser General Public License as published by
	the Free Software Foundation, either version 3 of the License, or
	(at your option) any later version.

	k3 is distributed in the hope that it will be useful,
	but WITHOUT ANY WARRANTY; without even the implied warranty of
	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
	GNU Lesser General Public License for more details.

	You should have received a copy of the GNU Lesser General Public License
	along with k3.  If not, see <http://www.gnu.org/licenses/>.
*/

/**i8259 interrupt management code. Minimal code to disable/enable and boot-time remap the interrupt requests.*/

#include <shared.h>

//Definitions
#define CMOS 0x70
#define CMOS_RD 0x71
#define CMOS_GET_SEC 0x0
#define PIC1 0x20
#define PIC2 0xa0
#define PIC_INIT 0x11
#define PIC1_REG 0x21
#define PIC2_REG 0xa1
#define DELAY_PRT 0x80

#define PIC0_CMD 0x20
#define PIC1_CMD 0xa0
#define PIC0_DAT 0x21
#define PIC1_DAT 0xa1


#define IRQMAP1 0x20
#define IRQMAP2 0x28

#define RMIRQ1 0x8
#define RMIRQ2 0x70

/* IRQ map:
PIC1:
tim		kbd		pic		sp2		sp1		pp2		dsk		pp1
0		0		1		1		1		1		1		1
0xFC
PIC2:
rtc		cga		RES		RES		RES		fpu		hdc		RES
1		1		1		1		1		1		0		1
0xBF
*/

#define MASKALL 0xff
#define ALLOWALL 0
int irq_restore_enable();
int irq_save_disable();

/**Functions used by the code below to abstract io to the PIC*/

/*write to the command register*/
static inline void write_cmd0(unsigned char val)
{
	outb(PIC0_CMD,val);
}

static inline void write_cmd1(unsigned char val)
{
	outb(PIC1_CMD,val);
}

/*Write to the data register*/
static inline void write_dat0(unsigned char val)
{
	outb(PIC0_DAT,val);
}

static inline void write_dat1(unsigned char val)
{
	outb(PIC1_DAT,val);
}

static inline unsigned char read_cmd0()
{
	return inb(PIC0_CMD);
}

static inline unsigned char read_cmd1()
{
	return inb(PIC1_CMD);
}

static inline unsigned char read_dat0()
{
	return inb(PIC0_DAT);
}

static inline unsigned char read_dat1()
{
	return inb(PIC1_DAT);
}

enum {statusSAVE,statusRESTORE};
/*The 8259 local status structure
masks store the actual masks when irqs are saved and actuals are restored from these masks

*/
static struct irq_st	{
	unsigned char mask1;
	unsigned char mask2;
	int status; /*This indicates whether we are on an irq_save*/
}irq_st;

void set_irq(unsigned char mask_register1,unsigned char mask_register2)
{
	write_dat0(mask_register1);
	write_dat1(mask_register2);
}

/*Remap the irqs during bootup so that they don't overlap the exceptions of intel till 0x19*/
static void irq_remap(unsigned char irq_start,unsigned char irq_start2)
{
    write_cmd0(PIC_INIT);
    write_cmd1(PIC_INIT);
    write_dat0(irq_start);
    write_dat1(irq_start2);
    write_dat0(0x04);
    write_dat1(0x02);
    write_dat0(0x01);
    write_dat1(0x01);
    write_dat0(0x0);
    write_dat1(0x0);
}

/*This is called when we come back to protected mode from real mode*/
int restore_irq_pm()
{
	int error;
	irq_remap(IRQMAP1,IRQMAP2);
	if((error=irq_restore_enable())) {
		return error;
	}
	return 0;
}

/*Going to real mode*/
int prepare_irq_rm()
{
	int error;
	if((error=irq_save_disable())) {
		return error;
	}
	irq_remap(RMIRQ1,RMIRQ2);
	return 0;
}

/*Enables an irq by its number (0-15)*/
int enable_irq(int irq_number)
{
	unsigned char pic;
	if(irq_number<8)	{
		pic=read_dat0();
		pic&=~(1<<irq_number);
		irq_st.mask1=pic;
		write_dat0(pic);
	}
	else if(irq_number<16)	{
		pic=read_dat1();
		pic&=~(1<<(irq_number-8));
		irq_st.mask2=pic;
		write_dat1(pic);
	}
	else
		return EPINV;
	return 0;
}

int disable_irq(int irq_number)
{

	unsigned char pic;
	if(irq_number<8)	{
		pic=read_dat0();
		pic|=1<<irq_number;
		irq_st.mask1=pic;
		write_dat0(pic);
	}
	else if(irq_number<16)	{
		pic=read_dat1();
		pic|=1<<(irq_number-8);
		irq_st.mask2=pic;
		write_dat1(pic);
	}
	else
		return EPINV;
	return 0;
}

/*Checks whether an irq is enabled*/
int is_irq(int irq_number)
{
	unsigned char pic;
	if(irq_number<8)	{
		pic=read_dat0();
		return !(pic&(1<<irq_number));
	}
	else if(irq_number<16)	{
		pic=read_dat1();
		return !(pic&(1<<(irq_number-8)));
	}
	else
		return EPINV;
	return 0;
}

/*Send an eoi to the master pic if irqs 0-7 ended and to both master and slave if irqs 8-15 ended*/
int irq_ended(int irq_number)
{
	if(irq_number<8)	{
		write_cmd0(0x20);
	} else if(irq_number<16)	{
		write_cmd1(0x20);
		write_cmd0(0x20);
	} else
		return EPINV;
	return 0;
}

/*Save the irqs in the masks*/
static int irq_save()
{
	if(irq_st.status==statusRESTORE)	{
		irq_st.mask1=read_dat0();
		irq_st.mask2=read_dat1();
		irq_st.status=statusSAVE;
	}
	else {
		return ESTATEINV;
	}
	return 0;
}

static int irq_restore()
{
	if(irq_st.status==statusSAVE)	{
		write_dat0(irq_st.mask1);
		write_dat1(irq_st.mask2);
		irq_st.status=statusRESTORE;
	}
	else {
		return ESTATEINV;
	}
	return 0;
}

/*If 1 or more irqs are enabled then this returs true*/
int irqs_enabled()
{
	int mask1,mask2;
	mask1=read_dat0();
	mask2=read_dat1();
	if(mask1==0xff && mask2==0xff)
		return 0;
	return 1;
}

/*Save and disable*/
int irq_save_disable()
{
	irq_save();
	set_irq(MASKALL,MASKALL);
	return 0;
}

/*Restore (which enables also)*/
int irq_restore_enable()
{
	return irq_restore();
}

int init_pic()
{
	irq_remap(IRQMAP1,IRQMAP2);
	irq_st.mask1=0;
	irq_st.mask2=0;
	set_irq(0,0); //All irqs are now enabled
	irq_st.status=statusRESTORE;
	return 0;
}

int deinit_pic()
{
	set_irq(0xff,0xff); //Stop all irqs
	return 0;
}
